//
// Created by entaoyang@163.com on 2019-07-26.
// Copyright (c) 2019 entao.dev. All rights reserved.
//

import Foundation
import UIKit

@objcMembers
class FileCache2Model: Model {
    dynamic var key: String = ""
    dynamic var apppath: String = ""

    override class func onTableDefine(_ table: Table) {
        table["key"].PrimaryKey()
    }
}

public class FileCache {
    public static let DIR: AppFile = {
        let f = AppFile(category: .cache, paths: "filecache")
        if !f.exist {
            f.mkdir()
        }
        return f
    }()
}

public extension FileCache {
    static var cacheSize: Long {
        var sz: Long = FileCache.DIR.size
        for s in DIR.listDeep() {
            sz += AppFile(file: DIR, paths: s).size
        }
        return sz
    }

    static func removeCache() {
        if FileCache.DIR.exist {
            FileCache.DIR.remove()
        }
        FileCache.DIR.mkdir()
    }

    static func makeCacheFile(_ filename: String) -> AppFile {
        AppFile(file: DIR, paths: filename)
    }

    static func getFile(key: String) -> AppFile? {
        if let s = get(key: key) {
            let f = AppFile(apppath: s)
            if !f.exist {
                remove(key: key, delFile: false)
            } else {
                return f
            }
        }
        return nil
    }

    static private func get(key: String) -> String? {
        FileCache2Model.oneKey(key)?.apppath
    }

    static func remove(key: String, delFile: Bool) {
        if let s = FileCache2Model.oneKey(key)?.apppath {
            FileCache2Model.delete("key" =? key)
            if delFile {
                AppFile(apppath: s).remove()
            }
        }
    }

    static func put(key: String, file: AppFile) {
        put(key: key, apppath: file.appPath)
    }

    static private func put(key: String, apppath: String) {
        let m = FileCache2Model()
        m.key = key
        m.apppath = apppath
        m.replace()
    }

    static func dump() {
        FileCache2Model.dumpTable()
    }

}


fileprivate class MemCacheItem {
    let key: String
    let value: Any
    let size: Int
    var time: Long

    init(key: String, value: Any, size: Int) {
        self.key = key
        self.value = value
        self.size = size
        time = Now.mills
    }
}

public class MemCache: NSObject {
    private var map: [String: MemCacheItem] = [:]
    private let limitSize: Int
    private let singleSize: Int
    private var itemsSize: Int = 0

    public init(totalLimitBytes: Int = 20.mb, singleLimitBytes: Int = 2.mb) {
        self.limitSize = totalLimitBytes
        self.singleSize = singleLimitBytes
        super.init()
    }

    public static let inst: MemCache = MemCache(totalLimitBytes: 20.mb, singleLimitBytes: 2.mb)
}

public extension MemCache {

    func dump() {
        logd("----------------------")
        logd("totalSize:", itemsSize)
        let ls = map.valueArray.sortedAsc {
            $0.time
        }
        for e in ls {
            logd("key:", e.key, " size:", e.size, " time:", e.time, " value:\(e.value)")
        }
        logd("dump end.")
    }


    func has(_ key: String) -> Bool {
        get(key) != nil
    }

    func get(_ key: String) -> Any? {
        self.enterSync()
        defer {
            self.exitSync()
        }
        if let item = map[key] {
            item.time = Now.mills
            return item.value
        }
        return nil
    }

    func put(key: String, image: UIImage?) {
        if let img = image {
            put(key, image, img.bytesCount)
        } else {
            remove(key: key)
        }
    }

    func put(_ key: String, _ value: Any?, _ size: Int) {
        self.enterSync()
        defer {
            self.exitSync()
        }
        if let v = value {
            if size > singleSize {
                return
            }
            if itemsSize + size > limitSize {
                makeSpace(size)
            }
            itemsSize += size
            map[key] = MemCacheItem(key: key, value: v, size: size)
        } else {
            remove(key: key)
        }
    }

    func remove(key: String) {
        self.enterSync()
        defer {
            self.exitSync()
        }
        if let item = map[key] {
            itemsSize -= item.size
            map.removeValue(forKey: key)
        }
    }

    private func makeSpace(_ size: Int) {
        let ls = map.valueArray.sortedAsc {
            $0.time
        }
        var total: Int = 0
        var itemList: [MemCacheItem] = []
        for item in ls {
            total += item.size
            itemList.append(item)
            if total >= size {
                break
            }
        }
        for item in itemList {
            remove(key: item.key)
        }

    }

}

public extension UIImage {
    var bytesCount: Int {
        if let a = self.cgImage {
            return a.height * a.bytesPerRow
        }
        return 0
    }
}
